/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package main.NER.Trie;

import java.io.Serializable;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import main.Concurrent.Iteration.IterationHelper;
import main.Concurrent.Iteration.IterationOperation;
import main.KBProcess.Fragment;
import main.NER.Branch;
import main.NER.ContextCandidate;
import main.Ontology.ElementOntologySet;
import main.Weighting.WeightingCandidates;

/**
 *
 * @author Isaac Osesina
 */
public class CandidateExtractor extends KBTrie implements Serializable, Cloneable {
    
    WeightingCandidates weightingClass;
    public static enum BOOTSTRAP{YES, NO};


    public CandidateExtractor(KBTrie kbTrie) {
        super(kbTrie);
        if(kbTrie != null) {
            weightingClass = new WeightingCandidates(kbTrie);
        }
    }
    
    /**
     * Returns a Map containing the regular expression of all entity types 
     * (ElementOntologySet) in this KBTrie
     * @return Map containing the regular expression of all entity types in this KBTrie
     */
    @Override
    public Map<ElementOntologySet, Pattern> getAllEntityTypeIDRegex() {
        return super.getAllEntityTypeIDRegex();
    }  
    
    public List<NER_Candidate> extractCandidates(String inString,
            BOOTSTRAP bootStrap) {
        if (!normalize) {
            normalizeModel();
        }
        Set<ContextCandidate> leftContextsSet = getLEFT_CONTEXT_TRIE().findContexts(
                inString, bootStrap);
        Set<ContextCandidate> rightContextsSet = getRIGHT_CONTEXT_TRIE()
                .findContexts(inString, bootStrap);
        return extractCandidates(bootStrap, inString, leftContextsSet,
                rightContextsSet);
    }
    
    

    private List<NER_Candidate> extractCandidates(BOOTSTRAP bootStrap,
            String inString, Set<ContextCandidate> leftContextsSet,
            Set<ContextCandidate> rightContextsSet) {
        List<NER_Candidate> candidates = null;

        if (leftContextsSet == null || rightContextsSet == null) {
            return candidates;
        }

        int length = leftContextsSet.size();
        CandidateExtractionOperation op = new CandidateExtractionOperation(
                bootStrap, inString, rightContextsSet);

        List<List<NER_Candidate>> threadedResult =
                IterationHelper.runIteration(
                        "CandidateExtractionOperation", 
                        leftContextsSet, 
                        op, 
                        new ArrayList<List<NER_Candidate>>(), 
                        length);
        candidates = new ArrayList<NER_Candidate>();
        for (List<NER_Candidate> cs : threadedResult) {
            candidates.addAll(cs);
        }
        
        int i = 0;
        for(NER_Candidate c : candidates) {
            c.setCandidateID(i);
            i++;
        }

        return candidates;
    }
    
    
    private Map<ElementOntologySet, Branch> getBranchIDList(
            List<Branch> branches) {
        Map<ElementOntologySet, Branch> out = null;
        if (branches != null) {
            out = new HashMap<ElementOntologySet, Branch>();
            for (Branch b : branches) {
                if (b != null) {
                    out.put(b.getBranchID(), b);
                }
            }
        }
        return out;
    }


    private List<Map.Entry<Integer, Integer>> getStartEnd(ContextCandidate cxt,
            ContextTrie.ORIENTATION orientation, int inStringLength) {
        List<Map.Entry<Integer, Integer>> start_end = null;
        if (cxt != null) {
            start_end = new ArrayList<Map.Entry<Integer, Integer>>();
            Matcher m = cxt.getMatcher();
            while (m.find()) {
                int start;
                int end;
                if (orientation == ContextTrie.ORIENTATION.LEFT) {
                    end = inStringLength - 2 - m.start();
                    start = inStringLength - m.end();
                } else {
                    start = m.start();
                    end = m.end();
                }
                start_end.add(new ContextTrie.Entry<Integer, Integer>(start, end + 1));
            }
            m.reset();
        }
        return start_end;
    }
    
    
    private List<Map.Entry<Integer, Integer>> getStartEnd(
            Set<ContextCandidate> contextSet, ContextTrie.ORIENTATION orientation,
            int inStringLength) {
        List<Map.Entry<Integer, Integer>> start_end = null;
        if (contextSet != null) {
            start_end = new ArrayList<Map.Entry<Integer, Integer>>();
            for (ContextCandidate cxt : contextSet) {
                List<Map.Entry<Integer, Integer>> startEnd = this.getStartEnd(cxt,
                        orientation, inStringLength);
                if (startEnd != null) {
                    start_end.addAll(startEnd);
                }
            }
        }
        return start_end;
    }
    
    
    private String getCandidateValue(String inString,
            Map.Entry<Integer, Integer> left, Map.Entry<Integer, Integer> right) {
        String candVal = null;
        if (inString != null && left != null && right != null) {
            // if start of right context > end of left context
            if (right.getKey() > left.getValue()) {
                candVal = inString.substring(left.getValue(), right.getKey());
            }
        }
        return candVal;
    }

    
    /**
     * This method initiates does recursive candidate extraction.
     * After each iteration chosen candidates are scored; candidates are kept depending on
     * the state of <code>BOOTSTRAP</code>. If BOOTSTRAP.NO all candidates with non-zero score
     * are kept. If BOOTSTRAP.YES, candidate with max score is kept.
     * The ontology set of kept candidate(s) is/are substituted in workingTestDoc (to help in 
     * extracting subsequent candidates and prevent repeated extraction of same candidate).
     * Recursion is terminated if consecutive iterations with BOOTSTRAP.NO and BOOTSTRAP.YES are
     * unable to extract candidates.
     * @param workingTestDoc Document to extract candidates from
     * @param weightingMethod Method chosen for scoring candidates
     * @return List of <code>NER_Candidate</code>
     */

    public synchronized List<NER_Candidate> extractCandidates(String workingTestDoc, String weightingMethod) {
//        String doublePad = UTEES_Index.PADChar + "" + UTEES_Index.PADChar;
//        if(!workingTestDoc.startsWith(doublePad)) {
//            workingTestDoc = doublePad + workingTestDoc;
//        }
//        if(!workingTestDoc.endsWith(doublePad)) {
//            workingTestDoc = workingTestDoc + doublePad;
//        }
        String originalDoc = workingTestDoc;
        return doExtract(originalDoc,  workingTestDoc, BOOTSTRAP.NO, new ArrayList<NER_Candidate>(), weightingMethod);
    }
    

    private synchronized List<NER_Candidate> doExtract(String originalDoc, String workingTestDoc, BOOTSTRAP bootStrap,
                                               List<NER_Candidate> candidates, String weightingMethod) {
        List<NER_Candidate> extractCandidates = extractCandidates(workingTestDoc, bootStrap);
        extractCandidates = candidateScore(bootStrap, extractCandidates, weightingMethod);
        extractCandidates = CandidateExtractorUtil.removeCandidateWithContextIntercept(originalDoc, extractCandidates, candidates, workingTestDoc);
        if(extractCandidates != null && !extractCandidates.isEmpty()) {
            if(bootStrap == BOOTSTRAP.NO) {
                //remove identical candidates
                extractCandidates = CandidateExtractorUtil.removeInterceptCandidateValue(extractCandidates);
                candidates.addAll(extractCandidates);
            }
            else {
                int maxCandIdx = CandidateExtractorUtil.findMaxCandidate(extractCandidates, candidates, workingTestDoc);
                if(maxCandIdx>=0) {
                    NER_Candidate maxCand = extractCandidates.get(maxCandIdx);
                    candidates.add(maxCand);
                    bootStrap = BOOTSTRAP.NO;
                }
                else {
                    extractCandidates.clear();
                }
            }
            workingTestDoc = CandidateExtractorUtil.tagTestDocument(originalDoc, candidates);
        }
        if( bootStrap == BOOTSTRAP.NO && (extractCandidates == null || extractCandidates.isEmpty()) ){
            bootStrap = BOOTSTRAP.YES;
        }
        else if( bootStrap == BOOTSTRAP.YES && (extractCandidates == null || extractCandidates.isEmpty()) ){
            return candidates;
        }

        doExtract(originalDoc, workingTestDoc, bootStrap, candidates, weightingMethod);
            
        return candidates;
    }
    
    

    private synchronized List<NER_Candidate> candidateScore(BOOTSTRAP bootStrap, List<NER_Candidate> candidates, String weightingMethod) {
        double lowerBoundAsSDFactor_intrinsic = 1;
        double lowerBoundAsSDFactor_LCxt = 1;
        double lowerBoundAsSDFactor_RCxt = 1;
        double lowerBoundAsSDFactor_Cxt = 1;
//        return weightingClass.scoreCandidates(bootStrap, candidates, weightingMethod, lowerBoundAsSDFactor_intrinsic,
//                                              lowerBoundAsSDFactor_LCxt, lowerBoundAsSDFactor_RCxt);
//        return weightingClass.scoreCandidates(bootStrap, candidates, weightingMethod, 0);
        return weightingClass.scoreCandidates(bootStrap, candidates, weightingMethod, lowerBoundAsSDFactor_Cxt, lowerBoundAsSDFactor_intrinsic);
    }
    
    
    private class CandidateExtractionOperation implements
            IterationOperation<ContextCandidate, List<NER_Candidate>, List<List<NER_Candidate>>> {
        protected BOOTSTRAP bootStrap;
        protected String inString;
        protected Set<ContextCandidate> rightContextsSet;

        public CandidateExtractionOperation(BOOTSTRAP bootStrap,
                String inString, Set<ContextCandidate> rightContextsSet) {
            this.bootStrap = bootStrap;
            this.inString = inString;
            this.rightContextsSet = rightContextsSet;
        }

        @Override
        public List<NER_Candidate> compute(int index,
                ContextCandidate leftContext) {

            int inStringLength = inString.length();

            List<Branch> left_youngestBranches = leftContext.getStem()
                    .youngestBranches();
            Map<ElementOntologySet, Branch> left_branchIDList = getBranchIDList(left_youngestBranches);

            List<NER_Candidate> result = new ArrayList<NER_Candidate>();
            if (left_branchIDList == null) {
                return result;
            }
            for (ContextCandidate rightContext : rightContextsSet) {

                if (bootStrap == BOOTSTRAP.YES
                        && leftContext.getStem().isAmbiguous()
                        && rightContext.getStem().isAmbiguous()) {
                    continue;
                }

                List<Branch> right_youngestBranches = rightContext.getStem()
                        .youngestBranches();
                Map<ElementOntologySet, Branch> right_branchIDList = getBranchIDList(right_youngestBranches);

                if (right_branchIDList == null) {
                    continue;
                }

                if (Collections.disjoint(left_branchIDList.keySet(),
                        right_branchIDList.keySet())) {
                    continue;
                }

                List<Map.Entry<Integer, Integer>> left_startEnd = getStartEnd(
                        leftContext, ContextTrie.ORIENTATION.LEFT, inStringLength);
                List<Map.Entry<Integer, Integer>> right_startEnd = getStartEnd(
                        rightContext, ContextTrie.ORIENTATION.RIGHT, inStringLength);

                if (left_startEnd == null || right_startEnd == null) {
                    continue;
                }

                for (Map.Entry<Integer, Integer> left : left_startEnd) {
                    for (Map.Entry<Integer, Integer> right : right_startEnd) {
                        String candVal = getCandidateValue(inString, left,
                                right);

                        if (candVal == null || candVal.isEmpty()) {
                            continue;
                        }

                        left_branchIDList.keySet().retainAll(
                                right_branchIDList.keySet());
                        for (ElementOntologySet branchID : left_branchIDList
                                .keySet()) {
                            Branch branch = left_branchIDList.get(branchID);
                            Fragment fragmentSample = branch
                                    .getFragmentSample();

                            if (fragmentSample == null) {
                                continue;
                            }

                            NER_Candidate candidate = new NER_Candidate(
                                    0, // This will be updated later to correct val
                                    leftContext.getElement(),
                                    rightContext.getElement(), left.getValue(),
                                    right.getKey(),
                                    fragmentSample.getLanguageType(),
                                    fragmentSample.getDocumentLabel(),
                                    fragmentSample.getEntityLabel(), candVal);
                            candidate.setEntityClassRegex(getAllEntityTypeIDRegex()
                                    .get(fragmentSample.getOntologies()));
                            candidate.setBootStrap(bootStrap);
                            candidate.setLeftContextEnd(left.getValue() - 1);
                            candidate.setRightContextStart(right.getKey());
                            candidate.setStem_LEFT(leftContext.getStem());
                            candidate.setStem_RIGHT(rightContext.getStem());
                            result.add(candidate);
                        }
                    }
                }
            }
            
            return result;
        }

        @Override
        public List<List<NER_Candidate>> aggregate(
                List<List<NER_Candidate>> current, int index,
                List<NER_Candidate> result) {
            current.set(index, result);
            return current;
        }
    }
    

}
